﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Microsoft.SqlServer.Server;

namespace RingBufferStream.Utils.Database
{
  public class TableValuedCollection<T> : List<T>, IEnumerable<SqlDataRecord>
    where T : class
  {
    // ReSharper disable StaticFieldInGenericType
    private static readonly PropertyInfo[] _properties;
    private static readonly SqlMetaData[] _metadata;

    [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
    static TableValuedCollection()
    {
      _properties = typeof(T).GetProperties();

      _metadata = new SqlMetaData[_properties.Length];
      for (int i = 0; i < _properties.Length; i++)
      {
        var info = _properties[i];
        SqlDbType dbType = GetSqlType(info.PropertyType);
        if (dbType == SqlDbType.VarBinary)
        {
          int maxLength = GetMaxLength(info);
          _metadata[i] = new SqlMetaData(info.Name, dbType, maxLength);
        }
        else
        {
          _metadata[i] = new SqlMetaData(info.Name, dbType);
        }
      }
    }

    private static int GetMaxLength(PropertyInfo info)
    {
      CustomAttributeData data = info.CustomAttributes.FirstOrDefault(attribute => attribute.AttributeType.Name.StartsWith("MaxLength", StringComparison.Ordinal));
      return data != null ? (int)data.ConstructorArguments[0].Value : 0;
    }

    IEnumerator<SqlDataRecord> IEnumerable<SqlDataRecord>.GetEnumerator()
    {
      var record = new SqlDataRecord(_metadata);
      foreach (T data in this)
      {
        for (int i = 0; i < _properties.Length; i++)
        {
          record.SetValue(i, _properties[i].GetValue(data, null));
        }
        yield return record;
      }
    }

    private static SqlDbType GetSqlType(Type type)
    {
      var val = SqlDbType.VarChar;
      if (type == typeof(int) || type == typeof(int?))
      {
        val = SqlDbType.Int;
      }
      else if (type == typeof(long) || type == typeof(long?))
      {
        val = SqlDbType.BigInt;
      }
      else if (type == typeof(byte[]))
      {
        val = SqlDbType.VarBinary;
      }
      else if (type == typeof(Boolean) || type == typeof(bool?))
      {
        val = SqlDbType.Bit;
      }
      else if (type == typeof(DateTime) || type == typeof(DateTime?))
      {
        val = SqlDbType.DateTime;
      }
      else if (type == typeof(Decimal))
      {
        val = SqlDbType.Decimal;
      }
      // Please refer to the following document to add other types
      // http://msdn.microsoft.com/en-us/library/ms131092.aspx
      return val;
    }
  }
}
